"""
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    created by lei.chang on '24/05/2023'
    comment: 流程节点相关
"""
import base64
from datetime import datetime

from filetype import filetype

from project.core.base import BodyParams
from project.core.exception import BizError, NoRecordError
from project.middleware.oss import Oss
from project.model.database.basic import NodeType
from project.model.database.flow import Flow
from project.model.database.node import Node, NodeGroup, NodeItem
from project.model.basic import Selector, Device
from project.service.record import RecordService


class NodeService:
    """
    """
    @staticmethod
    async def do_node_add(session, body_params: BodyParams):
        """
        :param session:
        :param body_params:
        :return:
        """
        if body_params.node_type:
            node_type = await NodeType.fetch_one(session, wheres=(NodeType.code == body_params.node_type))
            if not node_type:
                raise BizError('节点类型不存在')

        flow = await Flow.fetch_one(session, wheres=(Flow.id == body_params.flow_id))
        if not flow:
            raise BizError('指定的服务流程不存在')

        last_node_id = body_params.pop('last_node_id', 0)
        last_node = await Node.fetch_one(session, wheres=(Node.id == last_node_id))
        if last_node_id and not last_node:
            raise BizError('上一节点不存在')

        if last_node and last_node.flow_id != flow.id:
            raise BizError('上一节点服务流程与当前服务流程不匹配')

        if body_params.exists('custom_icon'):
            oss = Oss()
            name = datetime.now().strftime('%Y%m%d%H%M%S%f')
            try:
                content = base64.b64decode(body_params.pop('custom_icon'))
            except:
                raise BizError('base64解码失败，请检查格式是否正确')
            kind = filetype.guess(content)
            if not kind:
                raise BizError('上传失败，请上传图片格式文件')
            url = oss.upload_file(object_name=f'cariabot/icon_custom/{name}.{kind.extension}', bytes_file=content)
            # body_params.pop('title_icon')
            setattr(body_params, 'title_icon', url)

        node = await Node.create_modify(session, **body_params.dict(), commit=False)
        if not last_node:
            first_node = await Node.fetch_one(session, wheres=(Node.flow_id == body_params.flow_id, Node.is_first, ~Node.is_deleted))
            if first_node:
                first_node.is_first = 0
                node.next_node_id = first_node.id
            node.is_first = 1
        else:
            node.next_node_id = last_node.next_node_id if last_node.next_node_id else 0
            last_node.next_node_id = node.id

        node.code = str(node.id).zfill(6)
        node.flow_code = flow.code

        await session.commit()

        return node

    @staticmethod
    async def do_node_delete(session, node_id):
        """
        :param session:
        :param node_id:
        :return:
        """
        node = await Node.fetch_one(session, wheres=(Node.id == node_id, ~Node.is_deleted))
        if not node:
            raise BizError('节点不存在')
        # 上一节点
        last_node = await Node.fetch_one(session, wheres=(Node.next_node_id == node_id, ~Node.is_deleted))

        # 下一节点
        next_node = await Node.fetch_one(session, wheres=(Node.id == node.next_node_id, ~Node.is_deleted))

        # 第一个节点
        if node.is_first:
            # 将下一个节点设置成第一节点
            if next_node:
                next_node.is_first = True
        else:
            # 修改上一节点
            if last_node:
                last_node.next_node_id = 0
                if next_node:
                    last_node.next_node_id = next_node.id

        node.is_deleted = True
        node.next_node_id = 0

        await session.commit()

    @staticmethod
    async def do_node_group_delete(session, group_id):
        """ 删除节点组信息
        :param session:
        :param group_id:
        :return:
        """
        try:
            node_group = await NodeGroup.create_modify(session, wheres=(NodeGroup.id == group_id), no_record_error=True, is_deleted=1)
        except NoRecordError:
            raise BizError('节点分组不存在')

        # 删除关联项目的组信息
        node_items = await NodeItem.fetch_all(session, wheres=(NodeItem.group_id == node_group.id, ~NodeItem.is_deleted))
        for node_item in node_items:
            node_item.group_id = 0

        await session.commit()

    @staticmethod
    def sorted_nodes(nodes: list[Node]):
        """ 对节点列表进行排序
        :param nodes:
        :return:
        """
        items = list()
        if not nodes:
            return items

        node_map = {v.id: v for v in nodes}
        nodes.sort(key=lambda x: x.is_first, reverse=True)

        temp_node = first_node = nodes[0]
        items.append(first_node)
        while True:
            next_node = node_map.get(temp_node.next_node_id)
            if not next_node:
                break
            items.append(next_node)
            temp_node = next_node

        return items

    @staticmethod
    async def get_node_detail(session, flow_code, node_code, device: Device, node_name='', date='', time='', selectors: list[Selector] = None):
        """ 取得节点详情
        :param flow_code:
        :param node_code:
        :param device: 设备信息
        :param node_name:
        :param date:
        :param time:
        :param selectors:
        :return:
        """
        wheres = list()
        wheres.append(Node.flow_code == flow_code)
        wheres.append(Node.code == node_code)
        if node_name:
            wheres.append(Node.name.like(f'%{node_name}%'))

        current_node = await Node.fetch_one(session, wheres=wheres)
        if not current_node:
            return {}
        ret_data = current_node.dict()

        # 节点分组信息
        groups = []
        node_groups = await NodeGroup.fetch_all(session, wheres=(NodeGroup.node_id == current_node.id, ~NodeGroup.is_deleted), orders=(NodeGroup.id,))
        min_time = [min(v.meal_period['start_time']) for v in node_groups if v.meal_period and v.meal_period['start_time']]
        for node_group in node_groups:
            if node_group.meal_period:
                _time = datetime.now().strftime('%H:%M')
                if time:
                    _time = time
                elif date in ('tomorrow', 'after_tomorrow', '明日', '后日'):
                    _time = min_time

                if node_group.meal_period['start_time'] <= _time <= node_group.meal_period['end_time']:
                    groups.append(node_group.dict('id', 'title', 'image'))
            else:
                groups.append(node_group.dict('id', 'title', 'image'))
        ret_data['groups'] = groups

        # 商品信息
        node_items = await NodeItem.fetch_all(session, wheres=(NodeItem.node_id == current_node.id, ~NodeItem.is_deleted), orders=(NodeItem.id,))
        if current_node.timeset:
            ret_data['dates'] = current_node.timeset.get('dates', ['today'])
            items = current_node.meal_periods
        else:
            items = [v.dict('title', 'image', 'count', 'price', 'detail', 'count_upper_limit', 'is_count', 'is_spec', 'spec', 'group_id') for v in node_items]
        ret_data['items'] = items

        # 加工节点选择信息列表
        record = list()
        if selectors:
            record.append({'title': '服务地址', 'value': f'{device.room_no} 房'})

            node_codes = [v.node_code for v in selectors]
            nodes = await Node.fetch_all(session, wheres=(Node.flow_code == flow_code, Node.code.in_(node_codes)))
            node_map = {v.code: v for v in nodes}

            for selector in selectors:
                node = node_map.get(selector.node_code)
                if not node:
                    continue
                if node.node_type in ('confirm', 'success'):
                    continue
                # 图文列表、文本列表
                if node.node_type in ('image_display_select', 'text_picker'):
                    values = []
                    for item in selector.items:
                        values.append(item.dict())
                    # 图片列表
                    if node.node_type == 'image_display_select':
                        record.append({'title': node.name, 'item_type': 'list_image', 'values': values})
                    # 文本列表
                    elif node.node_type == 'text_picker':
                        record.append({'title': node.name, 'item_type': 'list_text', 'values': values})
                # 服务时间(康养)
                elif node.node_type in ('service_time_booking',):
                    if selector.items:
                        record.append({'title': node.name, 'item_type': 'service_time', 'value': selector.items[0].title, 'date': selector.items[0].date,
                                       'time_duration': selector.items[0].time_duration})
                # 时间段选择
                elif node.node_type in ('time_period_picker',):
                    if selector.items:
                        record.append({'title': node.name, 'item_type': 'time_period_str', 'value': selector.items[0].title, 'date': selector.items[0].date})
                else:
                    if selector.items:
                        record.append({'title': node.name, 'value': selector.items[0].title})

        # 无动态显示列表默认添加固定项
        if current_node.node_type not in ('image_display_select', 'text_picker'):
            flow = await Flow.fetch_one(session, wheres=(Flow.code == flow_code))
            record.append({'title': '服务内容', 'value': flow.name if flow else ''})
        ret_data['record'] = record

        return ret_data

    @staticmethod
    async def get_next_node_detail(session, flow_code, device: Device, node_code='', selectors: list[Selector] = None):
        """
        :param session:
        :param flow_code:
        :param node_code:
        :param device:
        :param selectors:
        :return:
        """
        if not node_code:
            next_node = await Node.fetch_one(session, wheres=(Node.flow_code == flow_code, Node.is_first, ~Node.is_deleted))
        else:
            current_node = await Node.fetch_one(session, wheres=(Node.flow_code == flow_code, Node.code == node_code, ~Node.is_deleted))
            if not current_node:
                raise BizError('无节点信息')

            # 提交服务记录(最后成功页)
            if current_node.node_type == 'success' and selectors:
                await RecordService.do_flow_record_submit(session, flow_code, device, selectors)

            if not current_node.next_node_id:
                return {}
            next_node = await Node.fetch_one(session, wheres=(Node.id == current_node.next_node_id, ~Node.is_deleted))

        if not next_node:
            return {}

        # 取得节点详情
        ret_data = dict()
        ret_data['node'] = await NodeService.get_node_detail(session, flow_code=flow_code, node_code=next_node.code, device=device, selectors=selectors)
        ret_data['response'] = next_node.title if next_node.node_type != 'text_displayer' else next_node.display_text
        ret_data['flow_code'] = flow_code

        return ret_data

    @staticmethod
    async def do_node_update(session, id, body_params):
        if body_params.exists('custom_icon'):
            oss = Oss()
            name = datetime.now().strftime('%Y%m%d%H%M%S%f')
            content = base64.b64decode(body_params.pop('custom_icon'))
            kind = filetype.guess(content)
            if not kind:
                raise BizError('上传失败，请上传图片格式文件')
            url = oss.upload_file(object_name=f'cariabot/icon_custom/{name}.{kind.extension}', bytes_file=content)
            # body_params.pop('title_icon')
            setattr(body_params, 'title_icon', url)

        await Node.create_modify(session,
                                 wheres=(Node.id == id),
                                 **body_params.dict(), no_record_error=True)
